package sketch.specs;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.ArrayType;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

import sketch.ast.ASTUtils;
import sketch.generator.ExhaustiveValueGenerator;
import sketch.generator.ValueGenerator;
import sketch.generator.experiment.ValueGroupGenerator;
import sketch.specs.annotation.Repeat;
import sketch.util.Checker;

public class SketchedRepeatBlock extends SketchedBlock {
	
	public static final String COMMENT_REPRESENTATION = Repeat.value();// "@Repeat";

	public SketchedRepeatBlock(Block realBlock) {
		super(realBlock);
	}


	@Override
	protected SketchedBlock newInstance(Block block) {
		return new SketchedRepeatBlock(block);
	}

	@Override
	protected List<Block> replicate() {
		//the return block list
		List<Block> retBlocks = new LinkedList<Block>();
		
		Block innerBlock = this.realBlock;
		//first get all declared vars in the outside blocks
		SpecValueDeclarationFinder finder = new SpecValueDeclarationFinder(innerBlock);
		List<VariableDeclarationStatement> varStmts = finder.getAllValueDeclaredStatements();
		
		Map<SimpleName, VariableDeclarationFragment> varMap = ASTUtils.extractDefinedVariableMapping(varStmts);
		Map<SimpleName, Type> typeMaps = ASTUtils.extractDefinedVariableTypeMapping(varStmts);
		Set<SimpleName> vars = varMap.keySet();
		
		//no var declared outside
		if(vars.isEmpty()) {
			retBlocks.add(ASTUtils.deepClone(innerBlock));
			return retBlocks;
		}
		
		//see if the vars declared outside is used in this block
		UsedVariableVisitor usedVarVisitor = new UsedVariableVisitor(vars);
		usedVarVisitor.visit(innerBlock);
		Collection<SimpleName> usedVars = usedVarVisitor.getUsedVariables();
		if(usedVars.isEmpty()) {
			retBlocks.add(ASTUtils.deepClone(innerBlock));
			return retBlocks;
		}
		
		//start to replace
		Map<SimpleName, List<SimpleName>> dumap = usedVarVisitor.getDUMap();
		//we need to create generator for each simpleName declaration
//		Map<SimpleName,  ValueGenerator<Expression>> nameGeneratorMap =
//			new LinkedHashMap<SimpleName,  ValueGenerator<Expression>>();
		Map<SimpleName, ValueGroupGenerator<Expression>> nameValueGroupGeneratorMap =
			new LinkedHashMap<SimpleName, ValueGroupGenerator<Expression>>();
		for(SimpleName simpleName : dumap.keySet()) {
			DeclaredValueExtractor extractor = new DeclaredValueExtractor(varMap.get(simpleName));
			//nameGeneratorMap.put(simpleName, extractor.getArgumentValueGenerator());
			nameValueGroupGeneratorMap.put(simpleName, extractor.getArgumentGroupValueGenerator());
		}
		
		//StepwiseGeneratorGroupGenerator<Expression> group = new StepwiseGeneratorGroupGenerator<Expression>(nameGeneratorMap, typeMaps);
		StepExhaustedGroupGenerator<Expression> group = new StepExhaustedGroupGenerator<Expression>(nameValueGroupGeneratorMap, typeMaps);
		
		while(group.hasNext()) {
			group.next();
			Block replicate_block = ASTUtils.deepClone(innerBlock);
			//Map<SimpleName, String> nameMap = new LinkedHashMap<SimpleName, String>();
			Map<SimpleName, Expression> expressionMap = new LinkedHashMap<SimpleName, Expression>();
			for(SimpleName name : nameValueGroupGeneratorMap.keySet()) {
				Object obj = group.lookupCurrentValue(name);
				Expression expr = null;
				if(obj instanceof Expression) {
					expr = (Expression)obj;
				} else if (obj instanceof List) {
					List<Expression> expressionList = (List<Expression>)obj; 
					AST ast = name.getAST();
					Type t = ASTUtils.deepClone(typeMaps.get(name));
					
					Checker.checkTrue(t instanceof ArrayType, "t should be an array type, but now it is: " + t.getClass());
					ArrayType at = (ArrayType)t;
					t.delete();
					ArrayCreation ac3 = ast.newArrayCreation();
					ac3.setType(at);
					ArrayInitializer ai = ast.newArrayInitializer();
					ac3.setInitializer(ai);
					for(Expression e : expressionList) {
					    ai.expressions().add(ast.newNumberLiteral(e.toString()));
					}
					//init the expression
				    expr = ac3;
					
				} else {
					throw new RuntimeException("Unexpected type!");
				}
				Checker.checkNull(expr, "The expression could not be  null, obj type: " + obj.getClass());
				expressionMap.put(name, expr);
			}
			//replace the name
			//System.out.println("expression map: " + expressionMap);
			SimpleNameReplaceVisitor visitor = new SimpleNameReplaceVisitor(expressionMap);
			visitor.visit(replicate_block);
			//the names in replicate_block has been changed
			retBlocks.add(replicate_block);
			//System.out.println("--- replicate_block --- ");
		}
		
		//return retBlocks;
		//combine blocks into a huge one!
		List<Block> retOneBlock = new LinkedList<Block>();
		
		Block mergedBlock = this.realBlock.getAST().newBlock();
		for(Block b : retBlocks) {
			mergedBlock.statements().add(b);
		}
		
		retOneBlock.add(mergedBlock);
		return retOneBlock;
	}
}
